package org.spamforces.utils;

import com.codeforces.commons.text.Patterns;
import com.codeforces.commons.text.StringUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.spamforces.dao.EmailDao;
import org.spamforces.dao.RecipientProviderDao;
import org.spamforces.dao.SmsDao;
import org.spamforces.model.*;

import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.util.*;

public class BroadcastHelper {
    private static final String TIMEANDDATECOM_URL_PARAM_NAME = "timeAndDateComUrl";

    public static String testTemplate(String templateStr, Map<String, String> params) throws Exception {
        Configuration cfg = new Configuration();
        cfg.setEncoding(Locale.US, "UTF-8");
        @SuppressWarnings("deprecation") Template tpl = new Template("test", new StringReader(templateStr), cfg, "UTF-8");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");
        tpl.process(params, writer);
        return new String(out.toByteArray(), "UTF-8");
    }

    public static List<RecipientProvider> getProviders(Broadcast broadcast, RecipientProviderDao recipientProviderDao) {
        List<RecipientProvider> list = new ArrayList<>();
        String[] s = Patterns.COMMA_PATTERN.split(broadcast.getEmailProvidersId());
        for (String x : s) {
            String y = x.trim();
            long id = Integer.parseInt(y);
            RecipientProvider ep = recipientProviderDao.find(id);
            list.add(ep);
        }
        return list;
    }

    public static void scheduleBroadcast(
            Broadcast broadcast, Map<Recipient, Map<String, String>> paramValueByKeyByRecipient,
            EmailDao emailDao, SmsDao smsDao, boolean ignoreRepeatedContacts) throws Exception {
        Map<String, String> paramValueByKey = extractMap(broadcast.getTemplateParams());

        scheduleBroadcastEmails(
                broadcast, paramValueByKeyByRecipient, emailDao, ignoreRepeatedContacts, paramValueByKey
        );

        scheduleBroadcastSmsList(
                broadcast, paramValueByKeyByRecipient, smsDao, ignoreRepeatedContacts, paramValueByKey
        );
    }

    private static void scheduleBroadcastEmails(
            Broadcast broadcast, Map<Recipient, Map<String, String>> paramValueByKeyByRecipient,
            EmailDao emailDao, boolean ignoreRepeatedContacts, Map<String, String> paramValueByKey) throws Exception {
        List<Email> emails = new ArrayList<>(paramValueByKeyByRecipient.size());
        Set<String> doneEmails = emailDao.emailsDoneOrQueueByTargetBroadcastId(broadcast.getId());

        for (Map.Entry<Recipient, Map<String, String>> recipientEntry : paramValueByKeyByRecipient.entrySet()) {
            Recipient recipient = recipientEntry.getKey();
            if (StringUtil.isBlank(recipient.getEmail())) {
                continue;
            }

            Map<String, String> p = new HashMap<>();
            p.putAll(paramValueByKey);
            p.putAll(recipientEntry.getValue());

            Email letter = new Email();
            letter.setBody(testTemplate(broadcast.getBodyTemplate(), p));
            letter.setSubject(testTemplate(broadcast.getSubjectTemplate(), p));
            letter.setBroadcastId(broadcast.getId());
            if (p.containsKey("fromEmail")) {
                letter.setFromEmail(p.get("fromEmail"));
            } else {
                letter.setFromEmail("noreply@codeforces.ru");
            }
            letter.setStatus(Status.QUEUE);
            letter.setHeaders("");
            letter.setTargetEmail(recipient.getEmail());
            //emailDao.save(letter);

            if (!ignoreRepeatedContacts || !doneEmails.contains(letter.getTargetEmail())) {
                emails.add(letter);

                if (emails.size() > 1000) {
                    emailDao.insert(emails);
                    emails.clear();
                }
            }
        }

        emailDao.insert(emails);
    }

    private static void scheduleBroadcastSmsList(
            Broadcast broadcast, Map<Recipient, Map<String, String>> paramValueByKeyByRecipient,
            SmsDao smsDao, boolean ignoreRepeatedContacts, Map<String, String> paramValueByKey) throws Exception {
        List<Sms> smsList = new ArrayList<>(paramValueByKeyByRecipient.size());
        Set<String> doneSmsSet = smsDao.smsSetDoneOrQueueByTargetBroadcastId(broadcast.getId());

        for (Map.Entry<Recipient, Map<String, String>> recipientEntry : paramValueByKeyByRecipient.entrySet()) {
            Recipient recipient = recipientEntry.getKey();
            if (StringUtil.isBlank(recipient.getPhone())) {
                continue;
            }

            Map<String, String> p = new HashMap<>();
            p.putAll(paramValueByKey);
            p.putAll(recipientEntry.getValue());

            Sms sms = new Sms();
            sms.setBody(testTemplate(broadcast.getBodyTemplate(), p));
            sms.setBroadcastId(broadcast.getId());
            sms.setStatus(Status.QUEUE);
            sms.setPhone(recipient.getPhone());
            //smsDao.save(sms);

            if (!ignoreRepeatedContacts || !doneSmsSet.contains(sms.getPhone())) {
                smsList.add(sms);

                if (smsList.size() > 1000) {
                    smsDao.insert(smsList);
                    smsList.clear();
                }
            }
        }

        smsDao.insert(smsList);
    }

    public static Map<String, String> extractMap(String stringParams) {
        Map<String, String> params = new HashMap<>();
        String[] pairs = stringParams.split("[\\r\\n]+");

        for (String s : pairs) {
            String t = s.trim();
            if (t.isEmpty()) {
                continue;
            }

            String key;
            String value;

            int equalPosition = t.indexOf('=');
            if (equalPosition < 0) {
                key = t.trim();
                value = "";
            } else {
                key = t.substring(0, equalPosition).trim();
                value = t.substring(equalPosition + 1).trim();
            }

            if (key.isEmpty()) {
                continue;
            }

            boolean isAnythingDo = true;
            while (isAnythingDo) {
                isAnythingDo = false;

                int valueIterator = 0;
                while (valueIterator < value.length()) {
                    if (value.charAt(valueIterator) == '$') {
                        if (valueIterator + 1 < value.length() && value.charAt(valueIterator + 1) == '{') {
                            int startPosition = valueIterator + 2;
                            int endPosition = startPosition;

                            while (endPosition < value.length() && value.charAt(endPosition) != '}') {
                                endPosition++;
                            }

                            if (endPosition < value.length() && value.charAt(endPosition) == '}') {
                                String currentParameter = value.substring(startPosition, endPosition);

                                if (!params.containsKey(currentParameter)) {
                                    throw new IllegalArgumentException("Unknown variable " + currentParameter + '.');
                                }

                                value = value.substring(0, startPosition - 2) + params.get(currentParameter) + value.substring(endPosition + 1);
                                isAnythingDo = true;
                                break;
                            }
                        }
                    }

                    valueIterator++;
                }
            }

            params.put(key, value);

            if (TIMEANDDATECOM_URL_PARAM_NAME.equals(key)) {
                try {
                    params.put(key + "_ru", TimeAndDateComParser.format(value, "ru"));
                    params.put(key + "_en", TimeAndDateComParser.format(value, "en"));
                } catch (IllegalArgumentException e) {
                    throw new IllegalArgumentException(e.getMessage());
                }
            }
        }

        return params;
    }
}
